# -*- coding: binary -*-

require 'rex/proto/mssql/client'
require 'metasploit/framework/tcp/client'

module Msf
###
#
# This module exposes methods for querying a remote MSSQL service
#
###
module Exploit::Remote::MSSQL
  include Exploit::Remote::MSSQL_COMMANDS
  include Exploit::Remote::Udp
  include Exploit::Remote::Tcp
  include Exploit::Remote::NTLM::Client
  include Msf::Exploit::Remote::Kerberos::Ticket::Storage
  include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options

  attr_accessor :mssql_client

  #
  # Creates an instance of a MSSQL exploit module.
  #
  def initialize(info = {})
    super

    # Register the options that all MSSQL exploits may make use of.
    register_options(
      [
        Opt::RHOST,
        Opt::RPORT(1433),
        OptString.new('USERNAME', [ false, 'The username to authenticate as', 'sa']),
        OptString.new('PASSWORD', [ false, 'The password for the specified username', '']),
        OptBool.new('USE_WINDOWS_AUTHENT', [ true, 'Use windows authentication (requires DOMAIN option set)', false]),
        # OptBool.new('TDSENCRYPTION', [ true, 'Use TLS/SSL for TDS data "Force Encryption"', false]), - TODO: support TDS Encryption
      ], Msf::Exploit::Remote::MSSQL)
    register_advanced_options(
      [
        OptPath.new('HEX2BINARY',   [ false, "The path to the hex2binary script on the disk",
          File.join(Msf::Config.data_directory, "exploits", "mssql", "h2b")
        ]),
        OptString.new('DOMAIN', [ true, 'The domain to use for windows authentication', 'WORKSTATION'], aliases: ['MssqlDomain']),
        *kerberos_storage_options(protocol: 'Mssql'),
        *kerberos_auth_options(protocol: 'Mssql', auth_methods: Msf::Exploit::Remote::AuthOption::MSSQL_OPTIONS),
      ], Msf::Exploit::Remote::MSSQL)
    register_autofilter_ports([ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ])
    register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
  end

  def set_session(client)
    print_status("Using existing session #{session.sid}")
    @mssql_client = client
  end

  #
  # This method sends a UDP query packet to the server and
  # parses out the reply packet into a hash
  #
  def mssql_ping(timeout=5)
    data = { }

    ping_sock = Rex::Socket::Udp.create(
      'PeerHost'  => rhost,
      'PeerPort'  => 1434,
      'Context'   =>
        {
          'Msf'        => framework,
          'MsfExploit' => self,
        })

    ping_sock.put("\x02")
    resp, _saddr, _sport = ping_sock.recvfrom(65535, timeout)
    ping_sock.close

    return data if not resp
    return data if resp.length == 0

    return mssql_ping_parse(resp)
  end

  #
  # Parse a 'ping' response and format as a hash
  #
  def mssql_ping_parse(data)
    res = []
    var = nil
    idx = data.index('ServerName')
    return res if not idx
    sdata = data[idx, (data.length - 1)]

    instances = sdata.split(';;')
    instances.each do |instance|
      rinst = {}
      instance.split(';').each do |d|
        if (not var)
          var = d
        else
          if (var.length > 0)
            rinst[var] = d
            var = nil
          end
        end
      end
      res << rinst
    end

    return res
  end

  #
  # Execute a system command via xp_cmdshell
  #
  def mssql_parse_tds_reply(data, info)
    @mssql_client.mssql_parse_tds_reply(data, info)
  end

  def mssql_parse_reply(data, info)
    @mssql_client.mssql_parse_reply(data, info)
  end

  #
  # Parse a single row of a TDS reply
  #
  def mssql_parse_tds_row(data, info)
    @mssql_client.mssql_parse_tds_row(data, info)
  end

  #
  # Parse a "ret" TDS token
  #
  def mssql_parse_ret(data, info)
    @mssql_client.mssql_parse_ret(data, info)
  end

  #
  # Parse a "done" TDS token
  #
  def mssql_parse_done(data, info)
    @mssql_client.mssql_parse_done(data, info)
  end

  #
  # Parse an "error" TDS token
  #
  def mssql_parse_error(data, info)
    @mssql_client.mssql_parse_error(data, info)
  end

  #
  # Parse an "environment change" TDS token
  #
  def mssql_parse_env(data, info)
    @mssql_client.mssql_parse_env(data, info)
  end

  #
  # Parse an "information" TDS token
  #
  def mssql_parse_info(data, info)
    @mssql_client.mssql_parse_info(data, info)
  end

  def mssql_xpcmdshell(cmd, doprint=false, opts={})
    @mssql_client.mssql_xpcmdshell(cmd, doprint, opts)
  end

  #
  # Upload and execute a Windows binary through MSSQL queries
  #
  def mssql_upload_exec(exe, debug=false)
    @mssql_client.mssql_upload_exec(exe, debug)
  end

  #
  # Upload and execute a Windows binary through MSSQL queries and Powershell
  #
  def powershell_upload_exec(exe, debug=false)
    @mssql_client.powershell_upload_exec(exe, debug)
  end

  #
  #this method send a prelogin packet and check if encryption is off
  #
  def mssql_prelogin(enc_error=false)
    @mssql_client.mssql_prelogin(enc_error)
  end

  #
  # This method connects to the server over TCP and attempts
  # to authenticate with the supplied username and password
  # The global socket is used and left connected after auth
  #
  def mssql_login(user='sa', pass='', db='', domain_name='')
    @mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT'])
    result = @mssql_client.mssql_login(user, pass, db, domain_name)
    add_socket(@mssql_client.sock) if @mssql_client.sock && !sockets.include?(@mssql_client.sock)
    result
  end

  def mssql_login_datastore(db=nil)
    mssql_login(datastore['USERNAME'], datastore['PASSWORD'], db || datastore['DATABASE'] || '', datastore['MssqlDomain'] || '')
  end
  #
  # Issue a SQL query using the TDS protocol
  #
  def mssql_query(sqla, doprint=false, opts={})
    @mssql_client.query(sqla, doprint, opts)
  end

  #
  # Nicely print the results of a SQL query
  #
  def mssql_print_reply(info)
    @mssql_client.mssql_print_reply(info)
  end

  def mssql_send_recv(req, timeout=15, check_status = true)
    @mssql_client.mssql_send_recv(req, timeout, check_status)
  end

  #
  # Encrypt a password according to the TDS protocol (encode)
  #
  def mssql_tds_encrypt(pass)
    # Convert to unicode, swap 4 bits both ways, xor with 0xa5
    Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
  end
end
end
